Skip to main content

Doing things On Beat

With Reactional it is extremely simple to get musical interactivity going, especially when it comes to beat based effects. There are several ways to do these things and this page aims to serve as an inspiration for different use cases.

GetCurrentBeat()%2

Inline

The most basic ways is to poll the the beat clock for the current beat every frame and update some parameter based on this.

float musicalDivision = 2;
float currBeat = Reactional.Playback.MusicSystem.GetCurrentBeat() % musicalDivision;
gameObject.transform.localScale = new Vector3(1f, 1f, 1f) * currBeat;

The above snippet will scale an object from 0-1 in size over 2 beats.

Using AnimationCurves

A slightly more elegant solution is to use an Animation Curve. Then you get better control of exactly how the beat clock will transform whatever aspect you want.

 void Start()
{
light = GetComponent<HDAdditionalLightData>();
}

void Update()
{
if(light == null)
return;

float currentBeat = Reactional.Playback.MusicSystem.GetCurrentBeat();
var value = animationCurve.Evaluate(((currentBeat + offset) % beatFrequency) / beatFrequency);
}

Here we can see how the currentBeat (+ an offset in case we want to put the flashing light to happen off-beat), is modulating the intensity of a light. A beatFrequency of 4 would modulo the currenBeat every time it reaches 4, then divided by 4 to normalise the value to 0-1 which the animationcurve expects.

The above code is taking from a forum post. For further explation see https://forum.reactionalmusic.com/t/enabling-dynamic-lighting-that-syncs-with-music-beats/

GetNextBeat

Another way is to check if the current beat has exceeded that of a target beat.

if (currentBeat > targetBeat) {
doStuff();
targetBeat = Reactional.Playback.MusicSystem.GetNextBeat(4);
}

In the above snippet, once currentBeat passes over targetBeat the condition will be true; doStuff will run and targetBeat will be reassigned to the next occurence of 4 beats. (i.e beat 4, 8, 12, 16, 20, 24 etc). If currentBeat is 9 then GetNextBeat(4) will return 12.

Using Coroutines

When using coroutines, we have included a helper to yield until the beat clock has passed a certain value.

yield return new WaitForNextBeat(4)

This is the most basic implementation of this. It will essentially check for the next instance of a where a 4th beat is, and wait until that happens before advancing. The method also has a second argument for offset. For instance an offset of WaitForNextBeat(4,-0.25f) will wait for the next 4th beat, minus one 16th note. This is useful if you want to wait for a time JUST before a strong beat so you can prepare an animation or schedule a sound effect.

Using Music Callbacks

The system also generates callbacks for BarBeat events. That is every time a beat happens it will report the current bar, and the current beat relative to that bar. There is a convenince call to check the BarBeat using `ReactionalEngine.Instance.CurrentBarBeat[] where the first array element is the Bar and second the Beat.

As for the callback, see the Musical Callback article, but in summary, to subscribe to the BarBeat event you need to do the following.

    public Text text;
public ReactionalEngine reactional;

private void OnEnable()
{
reactional.onBarBeat += UpdateBarBeat;
}

void UpdateBarBeat(double offset, int bar, int beatIndex)
{
text.text = bar + " " + beatIndex;
}

Now the UpdateBarBeat function will be triggered every time a new BarBeat event occurs.

Relative Offsets

You can also schedule things based on time, bot beat-time, and real time.

using Reactional.Playback;

MusicSystem.GetTimeToBeat(4); // will return how many beats until next occurence of 4 beats. i.e. At CurrentBeat being 15.5 and GetTimeToBeat(4), it will return 0.5 as the next occurence of 4ths is 16 beats.

MusicSystem.GetRealTimeToBeat(4); // same as above, but the returned value will be in seconds instead.

Syncing Animations

Any animation can be driven from the beat clock.

The caveat however is that the animation is using normalized time between 0-1.

Make sure the animator is not set to update by itself, and force it to update using the beat clock.

float currBeat = Reactional.Playback.MusicSystem.GetCurrentBeat() / 2; // Divided by 2 hear because I want the animation to last 2 beats.
m_Animator.Play(0, 0, currBeat % 1f);
m_Animator.Update(0f);